#include <catch.hpp>
#include "PolledTimeout.h"

#define mockverbose printf
#include "common/MockEsp.cpp"  // getCycleCount

// This won't work for
template<typename argT>
inline bool fuzzycomp(argT a, argT b)
{
    const argT epsilon = 10;
    return (std::max(a, b) - std::min(a, b) <= epsilon);
}

TEST_CASE("OneShot Timeout 500000000ns (0.5s)", "[polledTimeout]")
{
    using esp8266::polledTimeout::oneShotFastNs;
    using timeType = oneShotFastNs::timeType;
    timeType before, after, delta;

    Serial.println("OneShot Timeout 500000000ns (0.5s)");

    oneShotFastNs timeout(500000000);
    before = micros();
    while (!timeout.expired())
        yield();
    after = micros();

    delta = after - before;
    Serial.printf("delta = %u\n", delta);

    REQUIRE(fuzzycomp(delta / 1000, (timeType)500));

    Serial.print("reset\n");

    timeout.reset();
    before = micros();
    while (!timeout)
        yield();
    after = micros();

    delta = after - before;
    Serial.printf("delta = %u\n", delta);

    REQUIRE(fuzzycomp(delta / 1000, (timeType)500));
}

TEST_CASE("OneShot Timeout 3000000us", "[polledTimeout]")
{
    using esp8266::polledTimeout::oneShotFastUs;
    using timeType = oneShotFastUs::timeType;
    timeType before, after, delta;

    Serial.println("OneShot Timeout 3000000us");

    oneShotFastUs timeout(3000000);
    before = micros();
    while (!timeout.expired())
        yield();
    after = micros();

    delta = after - before;
    Serial.printf("delta = %u\n", delta);

    REQUIRE(fuzzycomp(delta / 1000, (timeType)3000));

    Serial.print("reset\n");

    timeout.reset();
    before = micros();
    while (!timeout)
        yield();
    after = micros();

    delta = after - before;
    Serial.printf("delta = %u\n", delta);

    REQUIRE(fuzzycomp(delta / 1000, (timeType)3000));
}

TEST_CASE("OneShot Timeout 3000ms", "[polledTimeout]")
{
    using esp8266::polledTimeout::oneShotMs;
    using timeType = oneShotMs::timeType;
    timeType before, after, delta;

    Serial.println("OneShot Timeout 3000ms");

    oneShotMs timeout(3000);
    before = millis();
    while (!timeout.expired())
        yield();
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)3000));

    Serial.print("reset\n");

    timeout.reset();
    before = millis();
    while (!timeout)
        yield();
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)3000));
}

TEST_CASE("OneShot Timeout 3000ms reset to 1000ms", "[polledTimeout]")
{
    using esp8266::polledTimeout::oneShotMs;
    using timeType = oneShotMs::timeType;
    timeType before, after, delta;

    Serial.println("OneShot Timeout 3000ms");

    oneShotMs timeout(3000);
    before = millis();
    while (!timeout.expired())
        yield();
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)3000));

    Serial.print("reset\n");

    timeout.reset(1000);
    before = millis();
    while (!timeout)
        yield();
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)1000));
}

TEST_CASE("Periodic Timeout 1T 3000ms", "[polledTimeout]")
{
    using esp8266::polledTimeout::periodicMs;
    using timeType = periodicMs::timeType;
    timeType before, after, delta;

    Serial.println("Periodic Timeout 1T 3000ms");

    periodicMs timeout(3000);
    before = millis();
    while (!timeout)
        yield();
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)3000));

    Serial.print("no reset needed\n");

    before = millis();
    while (!timeout)
        yield();
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)3000));
}

TEST_CASE("Periodic Timeout 10T 1000ms", "[polledTimeout]")
{
    using esp8266::polledTimeout::periodicMs;
    using timeType = periodicMs::timeType;
    timeType before, after, delta;

    Serial.println("Periodic 10T Timeout 1000ms");

    int counter = 10;

    periodicMs timeout(1000);
    before = millis();
    while (1)
    {
        if (timeout)
        {
            Serial.print("*");
            if (!--counter)
                break;
            yield();
        }
    }
    after = millis();

    delta = after - before;
    Serial.printf("\ndelta = %lu\n", delta);
    REQUIRE(fuzzycomp(delta, (timeType)10000));
}

TEST_CASE("OneShot Timeout 3000ms reset to 1000ms custom yield", "[polledTimeout]")
{
    using YieldOrSkipPolicy = esp8266::polledTimeout::YieldPolicy::YieldOrSkip;
    using oneShotMsYield    = esp8266::polledTimeout::timeoutTemplate<false, YieldOrSkipPolicy>;
    using timeType          = oneShotMsYield::timeType;
    timeType before, after, delta;

    Serial.println("OneShot Timeout 3000ms");

    oneShotMsYield timeout(3000);
    before = millis();
    while (!timeout.expired())
        ;
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)3000));

    Serial.print("reset\n");

    timeout.reset(1000);
    before = millis();
    while (!timeout)
        ;
    after = millis();

    delta = after - before;
    Serial.printf("delta = %lu\n", delta);

    REQUIRE(fuzzycomp(delta, (timeType)1000));
}
